// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
module tb;
  // dep packages
  import uvm_pkg::*;
  import dv_utils_pkg::*;
  import tl_agent_pkg::*;
  import chip_env_pkg::*;
  import top_pkg::*;
  import chip_test_pkg::*;

  // macro includes
  `include "uvm_macros.svh"
  `include "dv_macros.svh"
  `include "chip_hier_macros.svh"

  wire clk, rst_n;
  wire usb_clk, usb_rst_n;

  wire [NUM_GPIOS-1:0] gpio_pins;

  wire jtag_tck;
  wire jtag_tms;
  wire jtag_trst_n;
  wire jtag_tdi;
  wire jtag_tdo;

  wire spi_device_sck;
  wire spi_device_csb;
  wire spi_device_miso_o;
  wire spi_device_mosi_i;

  wire srst_n;
  wire jtag_spi_n;
  wire bootstrap;
  wire [7:0] io_dps;

  wire usb_dp0, usb_dn0, usb_sense0, usb_pullup0;

  bit stub_cpu;

  // interfaces
  clk_rst_if clk_rst_if(.clk(clk), .rst_n(rst_n));
  clk_rst_if usb_clk_rst_if(.clk(usb_clk), .rst_n(usb_rst_n));
  pins_if #(NUM_GPIOS) gpio_if(.pins(gpio_pins));
  pins_if #(1) srst_n_if(.pins(srst_n));
  pins_if #(1) jtag_spi_n_if(.pins(jtag_spi_n));
  pins_if #(1) bootstrap_if(.pins(bootstrap));
  spi_if spi_if(.rst_n(rst_n));
  tl_if   cpu_d_tl_if(.clk(clk), .rst_n(rst_n));
  uart_if uart_if();
  jtag_if jtag_if();

  // backdoors
  bind `ROM_HIER mem_bkdr_if rom_mem_bkdr_if();
  bind `FLASH0_MEM_HIER mem_bkdr_if flash0_mem_bkdr_if();
  bind `FLASH1_MEM_HIER mem_bkdr_if flash1_mem_bkdr_if();

  top_earlgrey_asic dut (
    // Clock and Reset
    .IO_CLK           (clk),
    .IO_RST_N         (rst_n),
    .IO_CLK_USB_48MHZ (usb_clk),

    // JTAG / SPI interface
    .IO_DPS0          (io_dps[0]),
    .IO_DPS1          (io_dps[1]),
    .IO_DPS2          (io_dps[2]),
    .IO_DPS3          (io_dps[3]),
    .IO_DPS4          (io_dps[4]),
    .IO_DPS5          (io_dps[5]),
    .IO_DPS6          (io_dps[6]),
    .IO_DPS7          (io_dps[7]),

    // UART interface
    .IO_URX           (uart_if.uart_rx),
    .IO_UTX           (uart_if.uart_tx),

    // USB interface
    .IO_USB_DP0       (usb_dp0),
    .IO_USB_DN0       (usb_dn0),
    .IO_USB_SENSE0    (usb_sense0),
    .IO_USB_PULLUP0   (usb_pullup0),

    // GPIO x 16 interface
    .IO_GP0           (gpio_pins[0 ]),
    .IO_GP1           (gpio_pins[1 ]),
    .IO_GP2           (gpio_pins[2 ]),
    .IO_GP3           (gpio_pins[3 ]),
    .IO_GP4           (gpio_pins[4 ]),
    .IO_GP5           (gpio_pins[5 ]),
    .IO_GP6           (gpio_pins[6 ]),
    .IO_GP7           (gpio_pins[7 ]),
    .IO_GP8           (gpio_pins[8 ]),
    .IO_GP9           (gpio_pins[9 ]),
    .IO_GP10          (gpio_pins[10]),
    .IO_GP11          (gpio_pins[11]),
    .IO_GP12          (gpio_pins[12]),
    .IO_GP13          (gpio_pins[13]),
    .IO_GP14          (gpio_pins[14]),
    .IO_GP15          (gpio_pins[15])
  );

  // connect sw_logger_if
  parameter string SwTypes[] = '{"rom", "sw"};
  generate
    for (genvar i = 0; i < 2; i++) begin: sw_logger_if_i
      bit             sw_log_valid;
      bit [TL_AW-1:0] sw_log_addr;

      sw_logger_if sw_logger_if (
        .clk          (`RAM_MAIN_HIER.clk_i),
        .rst_n        (`RAM_MAIN_HIER.rst_ni),
        .valid        (sw_log_valid),
        .addr_data    (`RAM_MAIN_HIER.wdata_i),
        .sw_log_addr  (sw_log_addr)
      );
      // TODO: RAM only looks at addr[15:2] - need to find a better way to capture it.
      assign sw_log_valid = !stub_cpu &&
                            `RAM_MAIN_HIER.req_i && `RAM_MAIN_HIER.write_i &&
                            (`RAM_MAIN_HIER.addr_i == sw_log_addr[15:2]);

      initial begin
        uvm_config_db#(virtual sw_logger_if)::set(
            null, "*.env", $sformatf("sw_logger_vif[%0s]", SwTypes[i]), sw_logger_if);
      end
    end
  endgenerate

  // connect the sw_test_status_if
  sw_test_status_if sw_test_status_if();
  assign sw_test_status_if.valid =  !stub_cpu &&
                                    `RAM_MAIN_HIER.req_i && `RAM_MAIN_HIER.write_i &&
                                    (`RAM_MAIN_HIER.addr_i ==
                                     sw_test_status_if.sw_test_status_addr[15:2]);
  assign sw_test_status_if.sw_test_status_val = `RAM_MAIN_HIER.wdata_i;

  // connect signals
  assign io_dps[0]  = jtag_spi_n ? jtag_tck : spi_device_sck;
  assign io_dps[1]  = jtag_spi_n ? jtag_tdi : spi_device_mosi_i;
  assign io_dps[3]  = jtag_spi_n ? jtag_tms : spi_device_csb;
  assign io_dps[4]  = jtag_trst_n;
  assign io_dps[5]  = srst_n;
  assign io_dps[6]  = jtag_spi_n;
  assign io_dps[7]  = bootstrap;
  assign spi_device_miso_o  = jtag_spi_n ? 1'b0 : io_dps[2];
  assign jtag_tdo           = jtag_spi_n ? io_dps[2] : 1'b0;

  assign jtag_tck         = jtag_if.tck;
  assign jtag_tms         = jtag_if.tms;
  assign jtag_trst_n      = jtag_if.trst_n;
  assign jtag_tdi         = jtag_if.tdi;
  assign jtag_if.tdo      = jtag_tdo;

  assign spi_device_sck     = spi_if.sck;
  assign spi_device_csb     = spi_if.csb;
  assign spi_device_mosi_i  = spi_if.mosi;
  assign spi_if.miso        = spi_device_miso_o;

  // TODO: USB-related signals, hookup an interface.
  assign usb_rst_n  = `USBDEV_HIER.rst_usb_48mhz_ni;
  assign usb_dp0    = 1'b1;
  assign usb_dn0    = 1'b0;
  assign usb_sense0 = 1'b0;

  initial begin
    // Set clk_rst_vifs
    // drive clk and rst_n from clk_if
    clk_rst_if.set_active();
    usb_clk_rst_if.set_active(.drive_clk_val(1'b1), .drive_rst_n_val(1'b0));
    uvm_config_db#(virtual clk_rst_if)::set(null, "*.env", "clk_rst_vif", clk_rst_if);
    uvm_config_db#(virtual clk_rst_if)::set(null, "*.env", "usb_clk_rst_vif", usb_clk_rst_if);

    // IO Interfaces
    uvm_config_db#(virtual pins_if #(NUM_GPIOS))::set(null, "*.env", "gpio_vif", gpio_if);
    uvm_config_db#(virtual uart_if)::set(null, "*.env.m_uart_agent*", "vif", uart_if);
    uvm_config_db#(virtual jtag_if)::set(null, "*.env.m_jtag_agent*", "vif", jtag_if);
    uvm_config_db#(virtual spi_if)::set(null, "*.env.m_spi_agent*", "vif", spi_if);
    uvm_config_db#(virtual tl_if)::set(null, "*.env.m_tl_agent*", "vif", cpu_d_tl_if);

    // Strap pins
    uvm_config_db#(virtual pins_if #(1))::set(
        null, "*.env", "srst_n_vif", srst_n_if);
    uvm_config_db#(virtual pins_if #(1))::set(
        null, "*.env", "jtag_spi_n_vif", jtag_spi_n_if);
    uvm_config_db#(virtual pins_if #(1))::set(
        null, "*.env", "bootstrap_vif", bootstrap_if);

    // Backdoors
    uvm_config_db#(virtual mem_bkdr_if)::set(
        null, "*.env", "mem_bkdr_vifs[Rom]", `ROM_HIER.rom_mem_bkdr_if);
    uvm_config_db#(virtual mem_bkdr_if)::set(
        null, "*.env", "mem_bkdr_vifs[FlashBank0]", `FLASH0_MEM_HIER.flash0_mem_bkdr_if);
    uvm_config_db#(virtual mem_bkdr_if)::set(
        null, "*.env", "mem_bkdr_vifs[FlashBank1]", `FLASH1_MEM_HIER.flash1_mem_bkdr_if);
    uvm_config_db#(virtual sw_test_status_if)::set(
        null, "*.env", "sw_test_status_vif", sw_test_status_if);

    $timeformat(-12, 0, " ps", 12);
    run_test();
  end

  // stub cpu environment
  // if enabled, clock to cpu is forced to 0
  // cpu_d_tl_if force are applied to cpu d tl interface
  initial begin
    void'($value$plusargs("stub_cpu=%0b", stub_cpu));
    if (stub_cpu) begin
      force `CPU_HIER.clk_i = 1'b0;
      force `CPU_HIER.tl_d_o = cpu_d_tl_if.h2d;
    end else begin
      force cpu_d_tl_if.h2d = `CPU_HIER.tl_d_o;
    end
  end
  assign cpu_d_tl_if.d2h = `CPU_HIER.tl_d_i;

endmodule
